/****************************************************************************
 * net/bluetooth/bt_gatt.c
 * Generic Attribute Profile handling.
 *
 *   Copyright (C) 2018 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * Ported from the Intel/Zephyr arduino101_firmware_source-v1.tar package
 * where the code was released with a compatible 3-clause BSD license:
 *
 *   Copyright (c) 2016, Intel Corporation
 *   All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <tinyara/config.h>

#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <debug.h>

#include <tinyara/bluetooth/bt_hci.h>
#include <tinyara/bluetooth/bt_core.h>
#include <tinyara/bluetooth/bt_buf.h>
#include <tinyara/bluetooth/bt_uuid.h>
#include <tinyara/bluetooth/bt_gatt.h>

#include "bt_hcicore.h"
#include "bt_conn.h"
#include "bt_keys.h"
#include "bt_l2cap.h"
#include "bt_att.h"

/****************************************************************************
 * Private Types
 ****************************************************************************/

struct gatt_incl_s {
	uint16_t start_handle;
	uint16_t end_handle;
	union {
		uint16_t uuid16;
		uint8_t uuid[16];
	} u;
} packed_struct;

struct gatt_chrc_s {
	uint8_t properties;
	uint16_t value_handle;
	union {
		uint16_t uuid16;
		uint8_t uuid[16];
	} u;
} packed_struct;

struct notify_data_s {
	FAR const void *data;
	size_t len;
	uint8_t handle;
};

/****************************************************************************
 * Private Data
 ****************************************************************************/

static FAR const struct bt_gatt_attr_s *g_db = NULL;
static size_t g_attr_count = 0;

/****************************************************************************
 * Public Functions
 ****************************************************************************/

void bt_gatt_register(FAR const struct bt_gatt_attr_s *attrs, size_t count)
{
	g_db = attrs;
	g_attr_count = count;
}

int bt_gatt_attr_read(FAR struct bt_conn_s *conn, FAR const struct bt_gatt_attr_s *attr, FAR void *buf, uint8_t buf_len, uint16_t offset, FAR const void *value, uint8_t value_len)
{
	uint8_t maxlen;
	uint8_t len;

	if (offset > value_len) {
		return -EINVAL;
	}

	len = buf_len;
	maxlen = value_len - offset;
	if (len > maxlen) {
		len = maxlen;
	}

	nvdbg("handle 0x%04x offset %u length %u\n", attr->handle, offset, len);

	memcpy(buf, value + offset, len);
	return len;
}

int bt_gatt_attr_read_service(FAR struct bt_conn_s *conn, FAR const struct bt_gatt_attr_s *attr, FAR void *buf, uint8_t len, uint16_t offset)
{
	FAR struct bt_uuid_s *uuid = attr->user_data;

	if (uuid->type == BT_UUID_16) {
		uint16_t uuid16 = BT_HOST2LE16(uuid->u.u16);

		return bt_gatt_attr_read(conn, attr, buf, len, offset, &uuid16, sizeof(uuid16));
	}

	return bt_gatt_attr_read(conn, attr, buf, len, offset, uuid->u.u128, sizeof(uuid->u.u128));
}

int bt_gatt_attr_read_include(FAR struct bt_conn_s *conn, FAR const struct bt_gatt_attr_s *attr, FAR void *buf, uint8_t len, uint16_t offset)
{
	FAR struct bt_gatt_include_s *incl = attr->user_data;
	struct gatt_incl_s pdu;
	uint8_t value_len;

	pdu.start_handle = BT_HOST2LE16(incl->start_handle);
	pdu.end_handle = BT_HOST2LE16(incl->end_handle);
	value_len = sizeof(pdu.start_handle) + sizeof(pdu.end_handle);

	if (incl->uuid->type == BT_UUID_16) {
		pdu.u.uuid16 = BT_HOST2LE16(incl->uuid->u.u16);
		value_len += sizeof(pdu.u.uuid16);
	} else {
		memcpy(pdu.u.uuid, incl->uuid->u.u128, sizeof(incl->uuid->u.u128));
		value_len += sizeof(incl->uuid->u.u128);
	}

	return bt_gatt_attr_read(conn, attr, buf, len, offset, &pdu, value_len);
}

int bt_gatt_attr_read_chrc(FAR struct bt_conn_s *conn, FAR const struct bt_gatt_attr_s *attr, FAR void *buf, uint8_t len, uint16_t offset)
{
	FAR struct bt_gatt_chrc_s *chrc = attr->user_data;
	struct gatt_chrc_s pdu;
	uint8_t value_len;

	pdu.properties = chrc->properties;
	pdu.value_handle = BT_HOST2LE16(chrc->value_handle);
	value_len = sizeof(pdu.properties) + sizeof(pdu.value_handle);

	if (chrc->uuid->type == BT_UUID_16) {
		pdu.u.uuid16 = BT_HOST2LE16(chrc->uuid->u.u16);
		value_len += sizeof(pdu.u.uuid16);
	} else {
		memcpy(pdu.u.uuid, chrc->uuid->u.u128, sizeof(chrc->uuid->u.u128));
		value_len += sizeof(chrc->uuid->u.u128);
	}

	return bt_gatt_attr_read(conn, attr, buf, len, offset, &pdu, value_len);
}

void bt_gatt_foreach_attr(uint16_t start_handle, uint16_t end_handle, bt_gatt_attr_func_t func, FAR void *user_data)
{
	size_t i;

	for (i = 0; i < g_attr_count; i++) {
		FAR const struct bt_gatt_attr_s *attr = &g_db[i];

		/* Check if attribute handle is within range */

		if (attr->handle < start_handle || attr->handle > end_handle) {
			continue;
		}

		if (func(attr, user_data) == BT_GATT_ITER_STOP) {
			break;
		}
	}
}

int bt_gatt_attr_read_ccc(FAR struct bt_conn_s *conn, FAR const struct bt_gatt_attr_s *attr, FAR void *buf, uint8_t len, uint16_t offset)
{
	FAR struct _bt_gatt_ccc_s *ccc = attr->user_data;
	uint16_t value;
	size_t i;

	for (i = 0; i < ccc->cfg_len; i++) {
		if (bt_addr_le_cmp(&ccc->cfg[i].peer, &conn->dst)) {
			continue;
		}

		value = BT_HOST2LE16(ccc->cfg[i].value);
		break;
	}

	/* Default to disable if there is no cfg for the peer */

	if (i == ccc->cfg_len) {
		value = 0x0000;
	}

	return bt_gatt_attr_read(conn, attr, buf, len, offset, &value, sizeof(value));
}

static void gatt_ccc_changed(FAR struct _bt_gatt_ccc_s *ccc)
{
	uint16_t value = 0x0000;
	int i;

	for (i = 0; i < ccc->cfg_len; i++) {
		if (ccc->cfg[i].value > value) {
			value = ccc->cfg[i].value;
		}
	}

	nvdbg("ccc %p value 0x%04x\n", ccc, value);

	if (value != ccc->value) {
		ccc->value = value;
		ccc->cfg_changed(value);
	}
}

int bt_gatt_attr_write_ccc(FAR struct bt_conn_s *conn, FAR const struct bt_gatt_attr_s *attr, FAR const void *buf, uint8_t len, uint16_t offset)
{
	FAR struct _bt_gatt_ccc_s *ccc = attr->user_data;
	FAR const uint16_t *data = buf;
	bool bonded;
	size_t i;

	if (len != sizeof(*data) || offset) {
		return -EINVAL;
	}

	if (bt_keys_get_addr(&conn->dst)) {
		bonded = true;
	} else {
		bonded = false;
	}

	for (i = 0; i < ccc->cfg_len; i++) {
		/* Check for existing configuration */

		if (!bt_addr_le_cmp(&ccc->cfg[i].peer, &conn->dst)) {
			break;
		}
	}

	if (i == ccc->cfg_len) {
		for (i = 0; i < ccc->cfg_len; i++) {
			/* Check for unused configuration */

			if (!ccc->cfg[i].valid) {
				bt_addr_le_copy(&ccc->cfg[i].peer, &conn->dst);

				/* Only set valid if bonded */

				ccc->cfg[i].valid = bonded;
				break;
			}
		}

		if (i == ccc->cfg_len) {
			nwdbg("No space to store CCC cfg\n");
			return -ENOMEM;
		}
	}

	ccc->cfg[i].value = BT_LE162HOST(*data);

	nvdbg("handle 0x%04x value %u\n", attr->handle, ccc->cfg[i].value);

	/* Update cfg if don't match */

	if (ccc->cfg[i].value != ccc->value) {
		gatt_ccc_changed(ccc);
	}

	return len;
}

int bt_gatt_attr_read_cep(FAR struct bt_conn_s *conn, FAR const struct bt_gatt_attr_s *attr, FAR void *buf, uint8_t len, uint16_t offset)
{
	FAR struct bt_gatt_cep_s *value = attr->user_data;
	uint16_t props = BT_HOST2LE16(value->properties);

	return bt_gatt_attr_read(conn, attr, buf, len, offset, &props, sizeof(props));
}

static uint8_t notify_cb(FAR const struct bt_gatt_attr_s *attr, FAR void *user_data)
{
	FAR struct notify_data_s *data = user_data;
	const struct bt_uuid_s uuid = {
		BT_UUID_16,
		{
			BT_UUID_GATT_CCC
		}
	};

	const struct bt_uuid_s chrc = {
		BT_UUID_16,
		{
			BT_UUID_GATT_CHRC
		}
	};

	FAR struct _bt_gatt_ccc_s *ccc;
	size_t i;

	if (bt_uuid_cmp(attr->uuid, &uuid)) {
		/* Stop if we reach the next characteristic */

		if (!bt_uuid_cmp(attr->uuid, &chrc)) {
			return BT_GATT_ITER_STOP;
		}

		return BT_GATT_ITER_CONTINUE;
	}

	/* Check attribute user_data must be of type struct _bt_gatt_ccc_s */

	if (attr->write != bt_gatt_attr_write_ccc) {
		return BT_GATT_ITER_CONTINUE;
	}

	ccc = attr->user_data;

	/* Notify all peers configured */

	for (i = 0; i < ccc->cfg_len; i++) {
		FAR struct bt_conn_s *conn;
		FAR struct bt_buf_s *buf;
		FAR struct bt_att_notify_s *nfy;

		/* TODO: Handle indications */

		if (ccc->value != BT_GATT_CCC_NOTIFY) {
			continue;
		}

		conn = bt_conn_lookup_addr_le_internal(&ccc->cfg[i].peer);
		if (!conn || conn->state != BT_CONN_CONNECTED) {
			continue;
		}

		buf = bt_att_create_pdu(conn, BT_ATT_OP_NOTIFY, sizeof(*nfy) + data->len);
		if (!buf) {
			nwdbg("No buffer available to send notification");
			bt_conn_release(conn);
			return BT_GATT_ITER_STOP;
		}

		nvdbg("conn %p handle 0x%04x\n", conn, data->handle);

		nfy = bt_buf_extend(buf, sizeof(*nfy));
		nfy->handle = BT_HOST2LE16(data->handle);

		bt_buf_extend(buf, data->len);
		memcpy(nfy->value, data->data, data->len);

		bt_l2cap_send(conn, BT_L2CAP_CID_ATT, buf);
		bt_conn_release(conn);
	}

	return BT_GATT_ITER_CONTINUE;
}

void bt_gatt_notify(uint16_t handle, FAR const void *data, size_t len)
{
	struct notify_data_s nfy;

	nfy.handle = handle;
	nfy.data = data;
	nfy.len = len;

	bt_gatt_foreach_attr(handle, 0xffff, notify_cb, &nfy);
}

static uint8_t connected_cb(FAR const struct bt_gatt_attr_s *attr, FAR void *user_data)
{
	FAR struct bt_conn_s *conn = user_data;
	FAR struct _bt_gatt_ccc_s *ccc;
	size_t i;

	/* Check attribute user_data must be of type struct _bt_gatt_ccc_s */

	if (attr->write != bt_gatt_attr_write_ccc) {
		return BT_GATT_ITER_CONTINUE;
	}

	ccc = attr->user_data;

	/* If already enabled skip */

	if (ccc->value) {
		return BT_GATT_ITER_CONTINUE;
	}

	for (i = 0; i < ccc->cfg_len; i++) {
		/* Ignore configuration for different peer */

		if (bt_addr_le_cmp(&conn->dst, &ccc->cfg[i].peer)) {
			continue;
		}

		if (ccc->cfg[i].value) {
			gatt_ccc_changed(ccc);
			return BT_GATT_ITER_CONTINUE;
		}
	}

	return BT_GATT_ITER_CONTINUE;
}

void bt_gatt_connected(FAR struct bt_conn_s *conn)
{
	nvdbg("conn %p\n", conn);
	bt_gatt_foreach_attr(0x0001, 0xffff, connected_cb, conn);
}

static uint8_t disconnected_cb(FAR const struct bt_gatt_attr_s *attr, FAR void *user_data)
{
	FAR struct bt_conn_s *conn = user_data;
	FAR struct _bt_gatt_ccc_s *ccc;
	size_t i;

	/* Check attribute user_data must be of type struct _bt_gatt_ccc_s */

	if (attr->write != bt_gatt_attr_write_ccc) {
		return BT_GATT_ITER_CONTINUE;
	}

	ccc = attr->user_data;

	/* If already disabled skip */

	if (!ccc->value) {
		return BT_GATT_ITER_CONTINUE;
	}

	for (i = 0; i < ccc->cfg_len; i++) {
		/* Ignore configurations with disabled value */

		if (!ccc->cfg[i].value) {
			continue;
		}

		if (bt_addr_le_cmp(&conn->dst, &ccc->cfg[i].peer)) {
			FAR struct bt_conn_s *tmp;

			/* Skip if there is another peer connected */

			tmp = bt_conn_lookup_addr_le_internal(&ccc->cfg[i].peer);
			if (tmp && tmp->state == BT_CONN_CONNECTED) {
				bt_conn_release(tmp);
				return BT_GATT_ITER_CONTINUE;
			}
		}
	}

	/* Reset value while disconnected */

	memset(&ccc->value, 0, sizeof(ccc->value));
	ccc->cfg_changed(ccc->value);

	nvdbg("ccc %p reseted\n", ccc);
	return BT_GATT_ITER_CONTINUE;
}

void bt_gatt_disconnected(FAR struct bt_conn_s *conn)
{
	nvdbg("conn %p\n", conn);
	bt_gatt_foreach_attr(0x0001, 0xffff, disconnected_cb, conn);
}

static void gatt_mtu_rsp(FAR struct bt_conn_s *conn, uint8_t err, FAR const void *pdu, uint16_t length, FAR void *user_data)
{
	bt_gatt_rsp_func_t func = user_data;

	func(conn, err);
}

static int gatt_send(FAR struct bt_conn_s *conn, FAR struct bt_buf_s *buf, bt_att_func_t func, FAR void *user_data, bt_att_destroy_t destroy)
{
	int err;

	err = bt_att_send(conn, buf, func, user_data, destroy);
	if (err) {
		ndbg("ERROR: Error sending ATT PDU: %d\n", err);
		bt_buf_release(buf);
	}

	return err;
}

int bt_gatt_exchange_mtu(FAR struct bt_conn_s *conn, bt_gatt_rsp_func_t func)
{
	FAR struct bt_att_exchange_mtu_req_s *req;
	FAR struct bt_buf_s *buf;
	uint16_t mtu;

	if (!conn || !func) {
		return -EINVAL;
	}

	buf = bt_att_create_pdu(conn, BT_ATT_OP_MTU_REQ, sizeof(*req));
	if (!buf) {
		return -ENOMEM;
	}

	/* Select MTU based on the amount of room we have in bt_buf_s including one
	 * extra byte for ATT header.
	 */

	mtu = bt_buf_tailroom(buf) + 1;

	nvdbg("Client MTU %u\n", mtu);

	req = bt_buf_extend(buf, sizeof(*req));
	req->mtu = BT_HOST2LE16(mtu);

	return gatt_send(conn, buf, gatt_mtu_rsp, func, NULL);
}

static void att_find_type_rsp(FAR struct bt_conn_s *conn, uint8_t err, FAR const void *pdu, uint16_t length, FAR void *user_data)
{
	FAR const struct bt_att_find_type_rsp_s *rsp = pdu;
	FAR struct bt_gatt_discover_params_s *params = user_data;
	uint16_t end_handle = 0;
	uint16_t start_handle;
	uint8_t i;

	nvdbg("err 0x%02x\n", err);

	if (err) {
		goto done;
	}

	/* Parse attributes found */

	for (i = 0; length >= sizeof(rsp->list[i]); i++, length -= sizeof(rsp->list[i])) {
		FAR const struct bt_gatt_attr_s *attr;

		start_handle = BT_LE162HOST(rsp->list[i].start_handle);
		end_handle = BT_LE162HOST(rsp->list[i].end_handle);

		nvdbg("start_handle 0x%04x end_handle 0x%04x\n", start_handle, end_handle);

		attr = (&(struct bt_gatt_attr_s)BT_GATT_PRIMARY_SERVICE(start_handle, params->uuid));

		if (params->func(attr, params) == BT_GATT_ITER_STOP) {
			goto done;
		}
	}

	/* Stop if could not parse the whole PDU */

	if (length > 0) {
		goto done;
	}

	/* Stop if over the range or the requests */

	if (end_handle >= params->end_handle) {
		goto done;
	}

	/* Continue for the last found handle */

	params->start_handle = end_handle;
	if (!bt_gatt_discover(conn, params)) {
		return;
	}

done:
	if (params->destroy) {
		params->destroy(params);
	}
}

/****************************************************************************
 * Name: bt_gatt_discover
 *
 * Description:
 *   This function implements the SIOCBTDISCOVER ioctl command for the
 *   GATT discovery.
 *
 ****************************************************************************/

int bt_gatt_discover(FAR struct bt_conn_s *conn, FAR struct bt_gatt_discover_params_s *params)
{
	FAR struct bt_buf_s *buf;
	FAR struct bt_att_find_type_req_s *req;
	FAR uint16_t *value;

	if (!conn || !params->uuid || !params->func || !params->start_handle || !params->end_handle) {
		return -EINVAL;
	}

	buf = bt_att_create_pdu(conn, BT_ATT_OP_FIND_TYPE_REQ, sizeof(*req));
	if (!buf) {
		return -ENOMEM;
	}

	req = bt_buf_extend(buf, sizeof(*req));
	req->start_handle = BT_HOST2LE16(params->start_handle);
	req->end_handle = BT_HOST2LE16(params->end_handle);
	req->type = BT_HOST2LE16(BT_UUID_GATT_PRIMARY);

	nvdbg("uuid 0x%04x start_handle 0x%04x end_handle 0x%04x\n", params->uuid->u.u16, params->start_handle, params->end_handle);

	switch (params->uuid->type) {
	case BT_UUID_16:
		value = bt_buf_extend(buf, sizeof(*value));
		*value = BT_HOST2LE16(params->uuid->u.u16);
		break;

	case BT_UUID_128:
		bt_buf_extend(buf, sizeof(params->uuid->u.u128));
		memcpy(req->value, params->uuid->u.u128, sizeof(params->uuid->u.u128));
		break;

	default:
		ndbg("ERROR: Unkown UUID type %u\n", params->uuid->type);
		bt_buf_release(buf);
		return -EINVAL;
	}

	return gatt_send(conn, buf, att_find_type_rsp, params, NULL);
}

static void att_read_type_rsp(FAR struct bt_conn_s *conn, uint8_t err, FAR const void *pdu, uint16_t length, FAR void *user_data)
{
	FAR const struct bt_att_read_type_rsp_s *rsp = pdu;
	FAR struct bt_gatt_discover_params_s *params = user_data;
	struct bt_uuid_s uuid;
	struct bt_gatt_chrc_s value;
	uint16_t handle = 0;

	nvdbg("err 0x%02x\n", err);

	if (err) {
		goto done;
	}

	/* Data can be either in UUID16 or UUID128 */

	switch (rsp->len) {
	case 7:					/* UUID16 */
		uuid.type = BT_UUID_16;
		break;

	case 21:					/* UUID128 */
		uuid.type = BT_UUID_128;
		break;

	default:
		ndbg("ERROR: Invalid data len %u\n", rsp->len);
		goto done;
	}

	/* Parse characteristics found */

	for (length--, pdu = rsp->data; length >= rsp->len; length -= rsp->len, pdu += rsp->len) {
		FAR const struct bt_gatt_attr_s *attr;
		FAR const struct bt_att_data_s *data = pdu;
		FAR struct gatt_chrc_s *chrc = (FAR void *)data->value;

		handle = BT_LE162HOST(data->handle);

		/* Handle 0 is invalid */

		if (!handle) {
			goto done;
		}

		/* Convert characteristic data, bt_gatt_chrc and gatt_chrc_s have different
		 * formats so the conversion have to be done field by field.
		 */

		value.properties = chrc->properties;
		value.value_handle = BT_LE162HOST(chrc->value_handle);
		value.uuid = &uuid;

		switch (uuid.type) {
		case BT_UUID_16:
			uuid.u.u16 = BT_LE162HOST(chrc->u.uuid16);
			break;

		case BT_UUID_128:
			memcpy(uuid.u.u128, chrc->u.uuid, sizeof(chrc->u.uuid));
			break;
		}

		nvdbg("handle 0x%04x properties 0x%02x value_handle 0x%04x\n", handle, value.properties, value.value_handle);

		/* Skip if UUID is set but doesn't match */

		if (params->uuid && bt_uuid_cmp(&uuid, params->uuid)) {
			continue;
		}

		attr = (&(struct bt_gatt_attr_s)BT_GATT_CHARACTERISTIC(handle, &value));

		if (params->func(attr, params) == BT_GATT_ITER_STOP) {
			goto done;
		}
	}

	/* Stop if could not parse the whole PDU */

	if (length > 0) {
		goto done;
	}

	/* Stop if over the requested range */

	if (params->start_handle >= params->end_handle) {
		goto done;
	}

	/* Continue to the next range */

	if (!bt_gatt_discover_characteristic(conn, params)) {
		return;
	}

done:
	if (params->destroy) {
		params->destroy(params);
	}
}

/****************************************************************************
 * Name: bt_gatt_discover_characteristic
 *
 * Description:
 *   This function implements the SIOCBTDISCOVER ioctl command for the
 *   GATT discover characteristics type.
 *
 ****************************************************************************/

int bt_gatt_discover_characteristic(FAR struct bt_conn_s *conn, FAR struct bt_gatt_discover_params_s *params)
{
	FAR struct bt_buf_s *buf;
	FAR struct bt_att_read_type_req_s *req;
	FAR uint16_t *value;

	if (!conn || !params->func || !params->start_handle || !params->end_handle) {
		return -EINVAL;
	}

	buf = bt_att_create_pdu(conn, BT_ATT_OP_READ_TYPE_REQ, sizeof(*req));
	if (!buf) {
		return -ENOMEM;
	}

	req = bt_buf_extend(buf, sizeof(*req));
	req->start_handle = BT_HOST2LE16(params->start_handle);
	req->end_handle = BT_HOST2LE16(params->end_handle);

	value = bt_buf_extend(buf, sizeof(*value));
	*value = BT_HOST2LE16(BT_UUID_GATT_CHRC);

	nvdbg("start_handle 0x%04x end_handle 0x%04x\n", params->start_handle, params->end_handle);

	return gatt_send(conn, buf, att_read_type_rsp, params, NULL);
}

static void att_find_info_rsp(FAR struct bt_conn_s *conn, uint8_t err, FAR const void *pdu, uint16_t length, FAR void *user_data)
{
	FAR const struct bt_att_find_info_rsp_s *rsp = pdu;
	FAR struct bt_gatt_discover_params_s *params = user_data;
	struct bt_uuid_s uuid;
	uint16_t handle = 0;
	uint8_t len;
	union {
		FAR const struct bt_att_info_16_s *i16;
		FAR const struct bt_att_info_128_s *i128;
	} info;

	nvdbg("err 0x%02x\n", err);

	if (err) {
		goto done;
	}

	/* Data can be either in UUID16 or UUID128 */

	switch (rsp->format) {
	case BT_ATT_INFO_16:
		uuid.type = BT_UUID_16;
		len = sizeof(info.i16);
		break;

	case BT_ATT_INFO_128:
		uuid.type = BT_UUID_128;
		len = sizeof(info.i128);
		break;

	default:
		ndbg("ERROR: Invalid format %u\n", rsp->format);
		goto done;
	}

	/* Parse descriptors found */

	for (length--, pdu = rsp->info; length >= len; length -= len, pdu += len) {
		FAR const struct bt_gatt_attr_s *attr;

		info.i16 = pdu;
		handle = BT_LE162HOST(info.i16->handle);

		switch (uuid.type) {
		case BT_UUID_16:
			uuid.u.u16 = BT_LE162HOST(info.i16->uuid);
			break;

		case BT_UUID_128:
			memcpy(uuid.u.u128, info.i128->uuid, sizeof(uuid.u.u128));
			break;
		}

		nvdbg("handle 0x%04x\n", handle);

		/* Skip if UUID is set but doesn't match */

		if (params->uuid && bt_uuid_cmp(&uuid, params->uuid)) {
			continue;
		}

		attr = (&(struct bt_gatt_attr_s)BT_GATT_DESCRIPTOR(handle, &uuid, 0, NULL, NULL, NULL));

		if (params->func(attr, params) == BT_GATT_ITER_STOP) {
			goto done;
		}
	}

	/* Stop if could not parse the whole PDU */

	if (length > 0) {
		goto done;
	}

	/* Next characteristic shall be after current value handle */

	params->start_handle = handle;
	if (params->start_handle < UINT16_MAX) {
		params->start_handle++;
	}

	/* Stop if over the requested range */

	if (params->start_handle >= params->end_handle) {
		goto done;
	}

	/* Continue to the next range */

	if (!bt_gatt_discover_descriptor(conn, params)) {
		return;
	}

done:
	if (params->destroy) {
		params->destroy(params);
	}
}

/****************************************************************************
 * Name: bt_gatt_discover_descriptor
 *
 * Description:
 *   This function implements the SIOCBTDISCOVER ioctl command for the
 *   GATT discover descriptor type.
 *
 ****************************************************************************/

int bt_gatt_discover_descriptor(FAR struct bt_conn_s *conn, FAR struct bt_gatt_discover_params_s *params)
{
	FAR struct bt_buf_s *buf;
	FAR struct bt_att_find_info_req_s *req;

	if (!conn || !params->func || !params->start_handle || !params->end_handle) {
		return -EINVAL;
	}

	buf = bt_att_create_pdu(conn, BT_ATT_OP_FIND_INFO_REQ, sizeof(*req));
	if (!buf) {
		return -ENOMEM;
	}

	req = bt_buf_extend(buf, sizeof(*req));
	req->start_handle = BT_HOST2LE16(params->start_handle);
	req->end_handle = BT_HOST2LE16(params->end_handle);

	nvdbg("start_handle 0x%04x end_handle 0x%04x\n", params->start_handle, params->end_handle);

	return gatt_send(conn, buf, att_find_info_rsp, params, NULL);
}

static void att_read_rsp(FAR struct bt_conn_s *conn, uint8_t err, FAR const void *pdu, uint16_t length, FAR void *user_data)
{
	bt_gatt_read_func_t func = user_data;

	nvdbg("err 0x%02x\n", err);

	if (err) {
		func(conn, err, NULL, 0);
		return;
	}

	func(conn, 0, pdu, length);
}

static int gatt_read_blob(FAR struct bt_conn_s *conn, uint16_t handle, uint16_t offset, bt_gatt_read_func_t func)
{
	FAR struct bt_buf_s *buf;
	FAR struct bt_att_read_blob_req_s *req;

	buf = bt_att_create_pdu(conn, BT_ATT_OP_READ_BLOB_REQ, sizeof(*req));
	if (!buf) {
		return -ENOMEM;
	}

	req = bt_buf_extend(buf, sizeof(*req));
	req->handle = BT_HOST2LE16(handle);
	req->offset = BT_HOST2LE16(offset);

	nvdbg("handle 0x%04x offset 0x%04x\n", handle, offset);

	return gatt_send(conn, buf, att_read_rsp, func, NULL);
}

int bt_gatt_read(FAR struct bt_conn_s *conn, uint16_t handle, uint16_t offset, bt_gatt_read_func_t func)
{
	FAR struct bt_buf_s *buf;
	FAR struct bt_att_read_req_s *req;

	if (!conn || !handle || !func) {
		return -EINVAL;
	}

	if (offset) {
		return gatt_read_blob(conn, handle, offset, func);
	}

	buf = bt_att_create_pdu(conn, BT_ATT_OP_READ_REQ, sizeof(*req));
	if (!buf) {
		return -ENOMEM;
	}

	req = bt_buf_extend(buf, sizeof(*req));
	req->handle = BT_HOST2LE16(handle);

	nvdbg("handle 0x%04x\n", handle);

	return gatt_send(conn, buf, att_read_rsp, func, NULL);
}

static void att_write_rsp(FAR struct bt_conn_s *conn, uint8_t err, FAR const void *pdu, uint16_t length, FAR void *user_data)
{
	bt_gatt_rsp_func_t func = user_data;

	nvdbg("err 0x%02x\n", err);

	func(conn, err);
}

static int gatt_write_cmd(FAR struct bt_conn_s *conn, uint16_t handle, FAR const void *data, uint16_t length)
{
	FAR struct bt_buf_s *buf;
	FAR struct bt_att_write_cmd_s *cmd;

	buf = bt_att_create_pdu(conn, BT_ATT_OP_WRITE_CMD, sizeof(*cmd) + length);
	if (!buf) {
		return -ENOMEM;
	}

	cmd = bt_buf_extend(buf, sizeof(*cmd));
	cmd->handle = BT_HOST2LE16(handle);
	memcpy(cmd->value, data, length);
	bt_buf_extend(buf, length);

	nvdbg("handle 0x%04x length %u\n", handle, length);

	return gatt_send(conn, buf, NULL, NULL, NULL);
}

int bt_gatt_write(FAR struct bt_conn_s *conn, uint16_t handle, FAR const void *data, uint16_t length, bt_gatt_rsp_func_t func)
{
	FAR struct bt_buf_s *buf;
	FAR struct bt_att_write_req_s *req;

	if (!conn || !handle) {
		return -EINVAL;
	}

	if (!func) {
		return gatt_write_cmd(conn, handle, data, length);
	}

	buf = bt_att_create_pdu(conn, BT_ATT_OP_WRITE_REQ, sizeof(*req) + length);
	if (!buf) {
		return -ENOMEM;
	}

	req = bt_buf_extend(buf, sizeof(*req));
	req->handle = BT_HOST2LE16(handle);
	memcpy(req->value, data, length);
	bt_buf_extend(buf, length);

	nvdbg("handle 0x%04x length %u\n", handle, length);

	return gatt_send(conn, buf, att_write_rsp, func, NULL);
}

void bt_gatt_cancel(FAR struct bt_conn_s *conn)
{
	bt_att_cancel(conn);
}

int bt_gatt_read_multiple(FAR struct bt_conn_s *conn, FAR const uint16_t *handles, size_t count, bt_gatt_read_func_t func)
{
	FAR struct bt_buf_s *buf;
	uint8_t i;

	if (!conn || conn->state != BT_CONN_CONNECTED) {
		return -ENOTCONN;
	}

	if (!handles || count < 2 || !func) {
		return -EINVAL;
	}

	buf = bt_att_create_pdu(conn, BT_ATT_OP_READ_MULT_REQ, count * sizeof(*handles));
	if (!buf) {
		return -ENOMEM;
	}

	for (i = 0; i < count; i++) {
		bt_buf_put_le16(buf, handles[i]);
	}

	return gatt_send(conn, buf, att_read_rsp, func, NULL);
}
